﻿#region 참조 네임스페이스

using Skying.Windows.API;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Skying.Resources
{
	/// <summary>
	/// 특정 실행 파일 혹은 리소스 파일에 포함되어 있는 리소스를 분석하기 위한 인터페이스를 제공한다.
	/// </summary>
	public class ResourceAnalyzer : IDisposable
	{
		#region 변수

		/// <summary>
		/// 리소스를 추출할 모듈의 핸들을 나타낸다.
		/// </summary>
		private IntPtr _moduleHandle;

		/// <summary>
		/// 로드된 모듈의 리소스 사전 목록을 나타낸다.
		/// </summary>
		private IDictionary<ResourceTypes, IList<Resource>> _resources;

		#endregion

		#region 생성자/소멸자

		public ResourceAnalyzer(string fileName)
		{
			// 대상 파일이 존재하지 않으면 진행할 수 없다.
			if (!File.Exists(fileName))
			{
				throw new FileNotFoundException();
			}

			// 전달된 경로의 파일에 대해서 모듈의 핸들을 가져온다.
			this._moduleHandle = Kernel32.LoadLibraryEx(fileName, IntPtr.Zero, 
				Kernel32.LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES |
				Kernel32.LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);

			if (this._moduleHandle.Equals(IntPtr.Zero))
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}

			// 리소스 목록을 초기화한다.
			this._resources = new Dictionary<ResourceTypes, IList<Resource>>();

			// 로드된 모듈에 포함되어 있는 리소스 유형을 열거한다.
			Kernel32.EnumResourceTypes(this._moduleHandle, new Kernel32.EnumResourceTypesDelegateEx(EnumerateResourceType), this);
		}

		~ResourceAnalyzer()
		{
			this.Dispose();
		}

		#endregion

		#region IDisposable 구현

		/// <summary>
		/// 로드된 모듈을 메모리에서 해제하고 리소스 사전 목록을 초기화한다.
		/// </summary>
		public void Dispose()
		{
			if (!this._moduleHandle.Equals(IntPtr.Zero))
			{
				Kernel32.FreeLibrary(this._moduleHandle);
			}

			this._resources.Clear();

			this._resources = null;
		}

		#endregion

		#region 리소스 열거 콜백

		/// <summary>
		/// <para><see cref="Skying.Windows.API.Kernel32.EnumResourceTypes"/>에 대한 콜백 함수를 구현한다.</para>
		/// <para>로드된 모듈에 포함되어 있는 리소스의 유형이 나열된다.</para>
		/// </summary>
		/// <param name="module">로드된 모듈의 핸들이 전달된다.</param>
		/// <param name="type">열거된 리소스의 타입이 전달된다.</param>
		/// <param name="lParam"><see cref="Skying.Windows.API.ResourceAnalyzer"/>의 인스턴스가 전달된다.</param>
		/// <returns>
		/// <para><see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>을 통해 리소스의 이름을 열거한 결과가 반환된다.</para>
		/// <para>리소스의 이름을 열거하지 못해 false가 반환되는 경우, 리소스 열거 작업은 더 이상 진행되지 않는다.</para>
		/// </returns>
		private static bool EnumerateResourceType(IntPtr module, IntPtr type, object lParam)
		{
			try
			{
				return Kernel32.EnumResourceNames(module, type, new Kernel32.EnumResourceNamesDelegateEx(EnumerateResourceNames), lParam);
			}
			catch (Exception)
			{
				return false;
			}
		}

		/// <summary>
		/// <para><see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>에 대한 콜백 함수를 구현한다.</para>
		/// <para>리소스 유형에 대한 리소스 이름이 나열된다.</para>
		/// </summary>
		/// <param name="module">로드된 모듈의 핸들이 전달된다.</param>
		/// <param name="type">열거된 리소스의 타입이 전달된다.</param>
		/// <param name="name">열거된 리소스의 이름이 전달된다.</param>
		/// <param name="lParam"><see cref="Skying.Windows.API.ResourceAnalyzer"/>의 인스턴스가 전달된다.</param>
		/// <returns>
		/// <para>로드된 리소스를 사전 목록에 정상적으로 추가했으면 true, 그렇지 않으면 false가 반환된다.</para>
		/// <para></para>
		/// </returns>
		private static bool EnumerateResourceNames(IntPtr module, IntPtr type, IntPtr name, object lParam)
		{
			try
			{
				ResourceAnalyzer analyzer = lParam as ResourceAnalyzer;

				analyzer.LoadResource(type, name);

				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		#endregion

		#region 리소스

		/// <summary>
		/// 리소스를 메모리에서 로드하여 리소스 목록을 저장한다.
		/// </summary>
		/// <param name="type"><see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>의 콜백으로부터 전달되는 리소스의 타입이다.</param>
		/// <param name="name"><see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>의 콜백으로부터 전달되는 리소스 이름의 핸들이다.</param>
		private void LoadResource(IntPtr type, IntPtr name)
		{
			// 메모리에서 로드된 리소스를 가져온다.
			Resource resource = Resource.GetInstance(this._moduleHandle, type, name);

			if (resource != null && Debugger.IsAttached)
			{
				Console.WriteLine("Resource > Name: {0}, Type: {1}, Alias: {2}", resource.Name, resource.ResourceType, resource.Alias);
			}

			if (resource != null)
			{
				if (!this._resources.ContainsKey(resource.ResourceType))
				{
					this._resources[resource.ResourceType] = new List<Resource>();
				}

				this._resources[resource.ResourceType].Add(resource);
			}
		}

		/// <summary>
		/// 특정 리소스 유형에 대한 리소스 목록을 가져온다.
		/// </summary>
		/// <param name="type">원하는 리소스 유형을 설정한다.</param>
		/// <returns>주어진 리소스 유형에 대한 리소스 목록이 반환된다.</returns>
		public IList<Resource> FindResources(ResourceTypes type)
		{
			return this._resources[type];
		}

		/// <summary>
		/// 문자열 리소스를 찾아 해당 문자열을 가져온다.
		/// </summary>
		/// <param name="segmentId">문자열 리소스의 세그먼트 아이디(리소스 아이디)를 설정한다.</param>
		/// <param name="stringId">문자열 리소스의 아이디를 설정한다.</param>
		/// <returns>주어진 세그먼트 아이디와 문자열 리소스의 아이디에 대한 문자열이 반환된다.</returns>
		public string FindString(int segmentId, int stringId)
		{
			if (!this._resources.ContainsKey(ResourceTypes.RT_STRING)) return null;

			IList<Resource> stringResources = this._resources[ResourceTypes.RT_STRING];

			StringResource stringResource = stringResources.SingleOrDefault(r => r.NameHandle.ToInt32().Equals(segmentId)) as StringResource;

			if (stringResource == null) return null;

			KeyValuePair<int, string> keyValue = stringResource.Values.SingleOrDefault(t => t.Key.Equals(stringId));

			return keyValue.Value;
		}

		#endregion
	}
}